#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <thread>
#include <uv.h>

#define _UVERR(call, err) std::cerr << call << " failed because " << uv_strerror(err) << std::endl
#define UVERR(call, err) { if (err) { _UVERR(call, err); } }
#define UVERRRET(call, err) { if (err) { _UVERR(call, err); return; } }
#define UVERRRETVAL(call, err) { if (err) { _UVERR(call, err); return err; } }
#define UVCALL(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERR(#call, err); } }
#define UVCALLRET(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERRRET(#call, err); } }
#define UVCALLRETVAL(call, ...) { if (auto err = call(__VA_ARGS__)) { UVERRRETVAL(#call, err); } }

class UVSocket {
    static inline std::shared_ptr<UVSocket> instance = nullptr;
    UVSocket() = default;

    struct WrittenBuffer {
        uv_write_t req;
        uv_buf_t buf;
    };

    union {
        struct sockaddr base;
        struct sockaddr_in addr;
    } addr;
    uv_loop_t *loop = uv_default_loop();
    uv_tcp_t server = {.data = this};
    uv_connect_t connect = {.data = this};
    uv_signal_t signal = {.data = this};
    std::unique_ptr<std::thread> thread = nullptr;
    std::promise<int32_t> connectRet;

    template<typename T>
    static UVSocket &GetInstance(T *that)
    {
        return *reinterpret_cast<UVSocket *>(that->data);
    }

    static void OnConnectted(uv_connect_t *connect, int32_t err)
    {
        UVERR(__func__, err);
        auto &that = GetInstance(connect);
        that.connectRet.set_value(err);
    }

    static void Written(uv_write_t *req, int32_t err)
    {
        UVERR(__func__, err);

        auto wbuffer = reinterpret_cast<WrittenBuffer *>(
            reinterpret_cast<char *>(req) - offsetof(WrittenBuffer, req));
        delete[] wbuffer->buf.base;
        delete wbuffer;
    }

    static void OnSignal(uv_signal_t *signal, int32_t signum)
    {
        if (signum == SIGINT) {
            auto &that = GetInstance(signal);
            UVCALL(uv_signal_stop, signal);
            uv_stop(that.loop);
        }
    }

    void RunThreadMain()
    {
        UVCALL(uv_run, loop, UV_RUN_DEFAULT);
    }

public:
    ~UVSocket()
    {
        uv_close(reinterpret_cast<uv_handle_t *>(&server), nullptr);
        raise(SIGINT);
        thread->join();
    }

    static UVSocket &GetInstance()
    {
        if (instance == nullptr) {
            static std::mutex mutex;
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) {
                instance = std::shared_ptr<UVSocket>(new UVSocket());
            }
        }
        return *instance;
    }

    int32_t Run(const char *address, const int32_t port)
    {
        UVCALLRETVAL(uv_tcp_init, loop, &server);
        UVCALLRETVAL(uv_ip4_addr, address, port, &addr.addr);
        UVCALLRETVAL(uv_tcp_connect, &connect, &server, &addr.base, OnConnectted);
        UVCALLRETVAL(uv_signal_init, loop, &signal);
        UVCALLRETVAL(uv_signal_start, &signal, OnSignal, SIGINT);
        thread = std::make_unique<std::thread>(std::bind(&UVSocket::RunThreadMain, this));
        return connectRet.get_future().get();
    }

    int32_t Write(const std::string &content)
    {
        auto wbuffer = new WrittenBuffer();
        wbuffer->buf.base = new char[content.size() + 1];
        wbuffer->buf.len = content.size() + 1;
        for (size_t i = 0; i <= content.size(); i++) {
            wbuffer->buf.base[i] = content.c_str()[i];
        }

        UVCALLRETVAL(uv_write, &wbuffer->req, connect.handle, &wbuffer->buf, 1, Written);
        return 0;
    }
};

class StreamWriter : public std::stringstream {
public:
    ~StreamWriter()
    {
        UVSocket::GetInstance().Write(str());
    }
};

int32_t main()
{
    auto &uvs = UVSocket::GetInstance();
    if (auto err = uvs.Run("127.0.0.1", 7000)) {
        return err;
    }

    StreamWriter() << "Hello";
    StreamWriter() << "UVSocket::";
    StreamWriter() << "StreamWriter";
    return 0;
}
